﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Engine.Sgml.Validation;
using Engine.Sgml.Validation.Rules;
using Engine.Sgml.Merging;
using Engine.Sgml.Matching;
using Engine.Sgml.Schema;

namespace Engine.Sgml.Allocation {
    internal sealed class InconsistentBlocksAllocationStrategy : IAllocationStrategy {
        List<InconsistentBlock> blockGroup;
        InAncestorAllocator inAncestorAllocator;
        InSiblingAllocator inSiblingAllocator;
        InBlockAllocator inBlockAllocator;

        public InconsistentBlocksAllocationStrategy(ValidationRuleCollection rules) {
            Guard.ArgumentNotNull(rules, "rules");
            blockGroup = new List<InconsistentBlock>();
            InconsistentBlockAllocator blockAllocator = new InconsistentBlockAllocator(rules);
            inAncestorAllocator = new InAncestorAllocator(blockAllocator);
            inSiblingAllocator = new InSiblingAllocator(blockAllocator);
            inBlockAllocator = new InBlockAllocator(blockAllocator);
            }

        public void Allocate(IList<InconsistentBlock> blocks, AllocationContext context) {
            Guard.ArgumentNotNull(blocks, "blocks");
            Guard.ArgumentNotNull(context, "context");
            do {
                for (int i = 0; i < blocks.Count; i++) {
                    SElement ancestor = blocks[i].Ancestor;
                    blockGroup.Add(blocks[i]);
                    for (int j = i + 1; j < blocks.Count; j++) {
                        if (object.ReferenceEquals(blocks[j].Ancestor, ancestor)) {
                            blockGroup.Add(blocks[j]);
                            i++;
                            }
                        else
                            break;
                        }
                    if (blockGroup.Count == 1) {
                        Allocate(blockGroup[0]);
                        }
                    else {
                        int middleLine = blockGroup.Count / 2;
                        for (int k = 0; k <= middleLine; k++) {
                            Allocate(blockGroup[k]);
                            }
                        for (int k = blockGroup.Count - 1; k > middleLine; k--) {
                            if (blockGroup[k].Count > 0)
                                Allocate(blockGroup[k]);
                            }
                        }
                    blockGroup.Clear();
                    }
                RemoveAllocatedBlocks(blocks);
                if (blocks.Count > 0)
                    AllocateChildElementsOfBlock(blocks);
                } while (blocks.Count > 0);
            blockGroup.Clear();
            }

        private void Allocate(InconsistentBlock block) {
            inAncestorAllocator.Initialize(block);
            inSiblingAllocator.Initialize(block);
            do {
                if (inSiblingAllocator.CanMove()) {
                    inSiblingAllocator.MovePrevious();
                    inSiblingAllocator.MoveNext();
                    if (inSiblingAllocator.Allocate())
                        return;
                    }
                if (inAncestorAllocator.MoveNext()) {
                    if (inAncestorAllocator.Allocate())
                        return;
                    }
                } while (inSiblingAllocator.CanMove() || inAncestorAllocator.CanMove());
            inBlockAllocator.Allocate(block);
            }

        private static void AllocateChildElementsOfBlock(IList<InconsistentBlock> blocks) {
            ModelValidator modelValidator = new ModelValidator();
            List<SElement> ancestors = new List<SElement>();
            for (int i = 0; i < blocks.Count; i++) {
                blocks[i].Relocate();
                blocks[i].Remove();
                }
            for (int i = 0; i < blocks.Count; i++) {
                SElement ancestor = blocks[i].Ancestor;
                if (ancestor.Parent != null && !ancestors.Exists(a => object.ReferenceEquals(a, ancestor)))
                    ancestors.Add(ancestor);
                }
            blocks.Clear();
            for (int i = 0; i < ancestors.Count; i++) {
                IList<InconsistentBlock> inconsistentBlocks = modelValidator.Validate(ancestors[i]);
                for (int j = 0; j < inconsistentBlocks.Count; j++) {
                    blocks.Add(inconsistentBlocks[j]);
                    }
                }
            }

        private static void RemoveAllocatedBlocks(IList<InconsistentBlock> blocks) {
            for (int i = 0; i < blocks.Count; i++) {
                if (blocks[i].Count == 0)
                    blocks.RemoveAt(i--);
                }
            }
        }
    }